package modules

import (
	"context"
	"fmt"
	"log"
	"net/http"
	"net/url"
	"os"
	"path/filepath"
	"sort"
	"strings"
	"sync"
)

// outputItems 输出排序后的文件和目录信息
func outputItems(w http.ResponseWriter, baseUrl string, items []FileInfo) {
	var totalCount int
	for _, item := range items {
		totalCount++
		filePath := strings.ReplaceAll(item.Path, "\\", "/")
		filePath = strings.ReplaceAll(filePath, " ", "%20")
		var fullUrl string
		var icon string
		if item.Type == "directory" {
			// 目录路径追加到 /cli/list/ 后
			fullUrl = fmt.Sprintf("%s/cli/list/%s", baseUrl, filePath)
			icon = "[目录]"
		} else {
			// 文件使用 download 路径
			fullUrl = fmt.Sprintf("%s/download/%s", baseUrl, filePath)
			icon = "[文件]"
		}
		fmt.Fprintf(w, "%s %s\n", icon, fullUrl)
	}
	// 发送完成消息
	dataMsg := fmt.Sprintf("\n目录列表获取完成，共找到 %d 个条目", totalCount)
	fmt.Fprintf(w, "%s\n", dataMsg)
}

func HandleCli(w http.ResponseWriter, r *http.Request) {
	// 若访问了 /cli 而不是 /cli/ 则自动重定向到 /cli/
	if r.URL.Path == "/cli" {
		http.Redirect(w, r, "/cli/", http.StatusFound)
		return
	}

	// 返回 cli/search 和 cli/list 的帮助信息
	PrintRequestInfo(r)
	w.Header().Set("Content-Type", "text/plain; charset=utf-8")

	// 获取基础URL (http://host:port)
	scheme := "http"
	if r.TLS != nil {
		scheme = "https"
	}
	baseUrl := fmt.Sprintf("%s://%s", scheme, r.Host)

	heading := fmt.Sprintf("%s - CLI 帮助 (通过浏览器访问 %s 体验更佳)", config.App.Name, baseUrl)
	splitLine := strings.Repeat("-", len(heading)-1)
	headerInfo := fmt.Sprintf("%s\n%s\n\n", splitLine, heading)

	helpMsg := fmt.Sprintf(`%s
使用方法:
  %s/cli/search/ 搜索文件，访问此地址查看检索帮助信息
  %s/cli/list/   浏览目录
`, headerInfo, baseUrl, baseUrl)

	w.Write([]byte(helpMsg))
}

// CliSearch 处理命令行搜索请求
func CliSearch(w http.ResponseWriter, r *http.Request) {
	// 若访问了 /cli/search 而不是 /cli/search/ 则自动重定向到 /cli/search/
	if r.URL.Path == "/cli/search" {
		http.Redirect(w, r, "/cli/search/", http.StatusFound)
		return
	}

	PrintRequestInfo(r)
	query := strings.TrimPrefix(r.URL.Path, "/cli/search/")
	query, _ = url.QueryUnescape(query)
	log.Printf("CLI 搜索请求，关键词: %s", query)

	// 设置 event-stream 响应头
	w.Header().Set("Content-Type", "text/event-stream; charset=utf-8")
	w.Header().Set("Cache-Control", "no-cache")
	w.Header().Set("Connection", "keep-alive")
	// 禁用响应压缩，避免因压缩导致消息延迟
	w.Header().Set("Content-Encoding", "identity")

	flusher, ok := w.(http.Flusher)
	if !ok {
		http.Error(w, "Streaming unsupported", http.StatusInternalServerError)
		return
	}

	// 获取基础URL (http://host:port)
	scheme := "http"
	if r.TLS != nil {
		scheme = "https"
	}
	baseUrl := fmt.Sprintf("%s://%s", scheme, r.Host)

	// 程序头信息
	heading := fmt.Sprintf("%s - CLI 检索 (通过浏览器访问 %s 体验更佳)", config.App.Name, baseUrl)
	splitLine := strings.Repeat("-", len(heading)-1)
	headerInfo := fmt.Sprintf("%s\n%s\n\n", splitLine, heading)

	// 检查关键字是否为空
	if query == "" {
		w.WriteHeader(http.StatusBadRequest)
		fmt.Fprintf(w, "%s\n\n", headerInfo)
		// 将下面的字符串拼接改为使用 ``` 包裹，这样可以保持字符串的格式，包括换行符和缩进`
		// 拼接字符串时，使用 \n 来表示换行，而不是使用 <br> 标签
		// 拼接字符串时，使用 \t 来表示缩进，而不是使用 &nbsp; 标签
		helpMsg := `错误：搜索关键字不能为空

使用方法:
	%s/cli/search/<关键字>      搜索文件
示例:
	%s/cli/search/document      搜索文件名包含'document'的文件
	%s/cli/search/document.exe  搜索文件名包含 document，扩展名为 exe 的文件
		`
		fmt.Fprintf(w, helpMsg, baseUrl, baseUrl, baseUrl)
		flusher.Flush()
		return
	}

	// 创建可取消的 context
	ctx, cancel := context.WithCancel(r.Context())
	// 监听客户端断开连接
	notify := w.(http.CloseNotifier).CloseNotify()
	go func() {
		<-notify
		cancel() // 客户端断开连接时取消 context
	}()

	fmt.Fprintf(w, "%s\n正在检索...\n\n", headerInfo)
	flusher.Flush()

	var wg sync.WaitGroup
	resultChan := make(chan FileInfo)

	// 为每个根目录启动搜索协程
	for _, rootDir := range config.RootDirs {
		rootName := filepath.Base(rootDir)
		wg.Add(1)
		go func(ctx context.Context, dir, q, name string) {
			defer wg.Done()
			dirResultChan := make(chan FileInfo)
			// 将 context 传递给 searchInDirectory
			go searchInDirectory(ctx, dir, q, name, dirResultChan) // 此处添加 go 关键字
			for item := range dirResultChan {
				select {
				case <-ctx.Done():
					return
				case resultChan <- item:
				}
			}
		}(ctx, rootDir, query, rootName)
	}

	// 启动一个协程，在所有搜索完成后关闭通道
	go func() {
		wg.Wait()
		close(resultChan)
	}()

	var totalCount int
	// 从通道接收结果并通过 event-stream 发送
	for {
		select {
		case <-ctx.Done():
			// 客户端断开连接或搜索取消，直接返回
			return
		case item, ok := <-resultChan:
			if !ok {
				// 通道已关闭，发送搜索完成消息
				data := fmt.Sprintf("搜索完成，共找到 %d 个结果", totalCount)
				fmt.Fprintf(w, "\n%s\n", data)
				flusher.Flush()
				return
			}
			totalCount++
			if totalCount > 1000 {
				cancel() // 超过 1000 条结果，取消搜索
				data := "检索结果超过 1000，中止检索"
				fmt.Fprintf(w, "\n%s\n", data)
				flusher.Flush()
				return
			}
			filePath := strings.ReplaceAll(item.Path, "\\", "/")
			filePath = strings.ReplaceAll(filePath, " ", "%20")
			fullUrl := fmt.Sprintf("%s/download/%s", baseUrl, filePath)
			fmt.Fprintf(w, "%s\n", fullUrl)
			flusher.Flush()
		}
	}
}

// CliList 处理命令行列出目录请求
func CliList(w http.ResponseWriter, r *http.Request) {
	PrintRequestInfo(r)

	// 获取基础URL (http://host:port)
	scheme := "http"
	if r.TLS != nil {
		scheme = "https"
	}
	baseUrl := fmt.Sprintf("%s://%s", scheme, r.Host)

	// 获取要列出的目录相对路径
	relativePath := strings.TrimPrefix(r.URL.Path, "/cli/list/")
	relativePath, _ = url.QueryUnescape(relativePath)

	// 程序头信息
	heading := fmt.Sprintf("%s - CLI 浏览 (通过浏览器访问 %s 体验更佳)", config.App.Name, baseUrl)
	splitLine := strings.Repeat("-", len(heading)-1)
	headerInfo := fmt.Sprintf("%s\n%s\n\n路径: /%s\n\n", splitLine, heading, relativePath)

	var targetPath, rootName string
	if relativePath == "" {
		// 没有指定路径，列出所有根目录
		var allItems []FileInfo
		for _, rootDir := range config.RootDirs {
			rootName = filepath.Base(rootDir)
			items := GetDirectoryItems(rootDir, rootName)
			allItems = append(allItems, items...)
		}
		// 排序，目录在前，文件在后
		sort.Slice(allItems, func(i, j int) bool {
			if allItems[i].Type == "directory" && allItems[j].Type != "directory" {
				return true
			}
			if allItems[i].Type != "directory" && allItems[j].Type == "directory" {
				return false
			}
			return allItems[i].Name < allItems[j].Name
		})
		w.WriteHeader(http.StatusOK)
		w.Write([]byte(headerInfo))
		outputItems(w, baseUrl, allItems)
		return
	}

	// 解析路径
	parts := strings.SplitN(relativePath, "/", 2)
	if len(parts) < 2 {
		log.Printf("指定路径 %s 格式不正确，应为 '根目录名/子路径'", relativePath)
		w.WriteHeader(http.StatusBadRequest)
		w.Write([]byte(headerInfo))
		// 手动写入错误信息，避免 http.Error 重复设置头
		w.Write([]byte("错误：指定路径格式不正确，应为 '根目录名/子路径'\n"))
		return
	}
	rootName = parts[0]
	subPath := parts[1]
	if _, exists := rootNames[rootName]; !exists {
		log.Printf("指定的根目录名 %s 不存在", rootName)
		w.WriteHeader(http.StatusBadRequest)
		w.Write([]byte(headerInfo))
		// 手动写入错误信息，避免 http.Error 重复设置头
		w.Write([]byte("错误：指定的根目录名不存在\n"))
		return
	}
	targetPath = filepath.Join(rootNames[rootName], subPath)
	info, err := os.Stat(targetPath)
	if err != nil || !info.IsDir() {
		log.Printf("指定路径 %s 不存在或非目录", targetPath)
		w.WriteHeader(http.StatusBadRequest)
		w.Write([]byte(headerInfo))
		// 手动写入错误信息，避免 http.Error 重复设置头
		w.Write([]byte("错误：指定路径不存在或非目录\n"))
		return
	}
	if IsPathOutOfScope(targetPath, rootName, w) {
		w.WriteHeader(http.StatusForbidden)
		w.Write([]byte(headerInfo))
		// 手动写入错误信息，避免 http.Error 重复设置头
		w.Write([]byte("错误：路径越权\n"))
		return
	}

	// 获取目录内容
	items := GetDirectoryItems(targetPath, rootName)
	// 排序，目录在前，文件在后
	sort.Slice(items, func(i, j int) bool {
		if items[i].Type == "directory" && items[j].Type != "directory" {
			return true
		}
		if items[i].Type != "directory" && items[j].Type == "directory" {
			return false
		}
		return items[i].Name < items[j].Name
	})
	w.WriteHeader(http.StatusOK)
	w.Write([]byte(headerInfo))
	outputItems(w, baseUrl, items)
}
